#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>

#include "pub_lib.h"
#include "ray_trace.h"

//#define DEBUG_VER

/* const values */
/* default values for scene
 Object()
        : materialType(DIFFUSE_AND_GLOSSY)
        , ior(1.3)
        , Kd(0.8)
        , Ks(0.2)
        , diffuseColor(0.2)
        , specularExponent(25)
*/
const float def_ior = 1.3f;
const float def_kd = 0.8f;
const float def_ks = 0.2f;
const Vector3 def_diffcolor = {0.2f, 0.2f, 0.2f};
const float def_exp = 25.0f;

const Vector3 back_col = {0.235294, 0.67451, 0.843137};

//sphere
Vector3 sp1_center = {-1, 0, -12};
float sp1_r = 2.0f;
Vector3 sp1_diffcolor = {0.6, 0.7, 0.8};

Vector3 sp2_center = {0.5, -0.5, -8};
float sp2_r = 1.5f;

Vector3 geye_pos = {0, 0, 0};
struct Light gLights[] = {
	{{-20, 70, 20}, {0.5 , 0.5, 0.5}},
	{{30, 50, -12}, {1, 1, 1}}
};


//global variables
struct Scene gScene;
struct Sphere gsph1, gsph2;
struct MeshTriangle gmesh;

void sphere_getSurfaceProperties(struct Object* obj,
	const Vector3 P, const Vector3 dir, const uint32_t index,
	Vector2* uv, Vector3* N, Vector2* st)
{
	//normalize(P - center);
	struct Sphere *p = (struct Sphere*)obj;
	Vector3 tmp = Vector3Subtract(P, p->center);
	*N = Vector3Normalize(tmp);
}

void triangle_getSurfaceProperties(struct Object* obj,
	const Vector3 P, const Vector3 dir, const uint32_t index,
	Vector2* uv, Vector3* N, Vector2* st)
{
	struct MeshTriangle* p = (struct MeshTriangle*)obj;

	Vector3 v0 = p->vertices[p->vertexIndex[index * 3]];
	Vector3 v1 = p->vertices[p->vertexIndex[index * 3 + 1]];
	Vector3 v2 = p->vertices[p->vertexIndex[index * 3 + 2]];

	Vector3 tmp = Vector3Subtract(v1, v0);
	Vector3 e0 = Vector3Normalize(tmp);//normalize(v1 - v0);

	tmp = Vector3Subtract(v2, v1);
	Vector3 e1 = Vector3Normalize(tmp);//normalize(v2 - v1);

	//N = normalize(crossProduct(e0, e1));
	tmp = Vector3CrossProduct(e0, e1);
	*N = Vector3Normalize(tmp);

	Vector2 st0 = p->stCoordinates[p->vertexIndex[index * 3]];
	Vector2 st1 = p->stCoordinates[p->vertexIndex[index * 3 + 1]];
	Vector2 st2 = p->stCoordinates[p->vertexIndex[index * 3 + 2]];
	Vector2 a, b, c;
	//st = st0 * (1 - uv.x - uv.y) + st1 * uv.x + st2 * uv.y;
	a = Vector2Scale(st0, 1 - uv->x - uv->y);
	b = Vector2Scale(st1, uv->x);
	c = Vector2Scale(st2, uv->y);
	st->x = a.x + b.x + c.x;
	st->y = a.y + b.y + c.y;
}

Vector3 default_evalDiffuseColor(struct Object* obj, const Vector2 st)
{
	return obj->diffuseColor;
}

Vector3 triangle_evalDiffuseColor(struct Object* obj, const Vector2 st)
{
	float scale = 5;
	int a, b, pattern;;
	a = fmodf(st.x * scale, 1) > 0.5;
	b = fmodf(st.y * scale, 1) > 0.5;
	pattern = a ^ b;
	Vector3 v0 = {0.815, 0.235, 0.031};
	Vector3 v1 = {0.937, 0.937, 0.231};
	return Vector3Lerp(v0, v1, (float)pattern);
}


void init_model(struct Object *p,
	enum MaterialType materialType,
	float ior, float Kd, float Ks,
	Vector3 diffuseColor,
	float specularExponent
)
{
	p->destroy = NULL;
	p->intersect = default_intersect;
	p->evalDiffuseColor = default_evalDiffuseColor;

	p->materialType = materialType;
	p->ior = ior;
	p->Kd = Kd;
	p->Ks = Ks;
	p->diffuseColor = diffuseColor;
	p->specularExponent = specularExponent;
}

void init_sphere(struct Sphere *p, Vector3 center, float r)
{
	init_model(&p->obj, DIFFUSE_AND_GLOSSY,
		def_ior, def_kd, def_ks, def_diffcolor, def_exp);

	p->center = center;
	p->radius = r;
	p->radius2 = r * r;
	p->obj.intersect = sphere_intersect;
	p->obj.getSurfaceProperties = sphere_getSurfaceProperties;
}

bool triangle_intersect(struct Object* obj,
	const Vector3 orig, const Vector3 dir,
	float* tnear, uint32_t* index, Vector2* uv)
{
	struct MeshTriangle *p = (struct MeshTriangle*)obj;

	bool intersect = false;
	Vector3 v0, v1, v2;
	float t, u, v;

	for (uint32_t k = 0; k < p->numTriangles; k++)
	{
		v0 = p->vertices[p->vertexIndex[k * 3]];
		v1 = p->vertices[p->vertexIndex[k * 3 + 1]];
		v2 = p->vertices[p->vertexIndex[k * 3 + 2]];
		if (rayTriangleIntersect(v0, v1, v2, orig, dir, &t, &u, &v) && t < *tnear)
		{
			*tnear = t;
			uv->x = u;
			uv->y = v;
			*index = k;
			intersect |= true;
		}
	}
	return intersect;
}

void init_triangle(struct MeshTriangle *p,
	Vector3* verts, uint32_t* vertsIndex, uint32_t numTris, const Vector2* st)
{
	//init with default values
	init_model(&p->obj, DIFFUSE_AND_GLOSSY,
		def_ior, def_kd, def_ks, def_diffcolor, def_exp);
	p->obj.intersect = triangle_intersect;
	p->obj.getSurfaceProperties = triangle_getSurfaceProperties;
	p->obj.evalDiffuseColor = triangle_evalDiffuseColor;

	uint32_t maxIndex = 0;
	for (uint32_t i = 0; i < numTris * 3; ++i)
		if (vertsIndex[i] > maxIndex)
			maxIndex = vertsIndex[i];
	maxIndex += 1;

	uint32_t cnt;
	cnt = sizeof(Vector3) * maxIndex;
	p->vertices = (Vector3*)malloc(cnt);
	memcpy(p->vertices, verts, cnt);

	cnt = sizeof(uint32_t) * (numTris * 3);
	p->vertexIndex = (uint32_t*)malloc(cnt);
	memcpy(p->vertexIndex, vertsIndex, cnt);
	p->numTriangles = numTris;

	cnt = sizeof(Vector2) * maxIndex;
	p->stCoordinates = (Vector2*)malloc(cnt);
	memcpy(p->stCoordinates, st, cnt);
}

void exit_triangle(struct Object* objs)
{
	struct MeshTriangle *p = (struct MeshTriangle *)objs;
	free(p->vertices);
	free(p->vertexIndex);
	free(p->stCoordinates);
}

// In the main function of the program, we create the scene (create objects and lights)
// as well as set the options for the render (image width and height, maximum recursion
// depth, field-of-view, etc.). We then call the render function().
void init_scene(struct Scene *pGscene)
{
	INIT_LIST_HEAD(&pGscene->objects);

	pGscene->eye_pos = geye_pos;
	pGscene->fov = 90;
	/* 1280 * 960*/
	pGscene->width = 1280;
	pGscene->height = 960;

    pGscene->backgroundColor = back_col;
    pGscene->maxDepth = 5;
    pGscene->bias = 0.00001;

	//init Modules: 2 spheres
	init_sphere(&gsph1, sp1_center, sp1_r);
	gsph1.obj.materialType = DIFFUSE_AND_GLOSSY;
	gsph1.obj.diffuseColor = sp1_diffcolor;

	init_sphere(&gsph2, sp2_center, sp2_r);
	gsph2.obj.ior = 1.5f;
	gsph2.obj.materialType = REFLECTION_AND_REFRACTION;

	Vector3 verts[4] = {{-5,-3,-6}, {5,-3,-6}, {5,-3,-16}, {-5,-3,-16}};
	uint32_t vertIndex[6] = {0, 1, 3, 1, 2, 3};
	Vector2 st[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};

	init_triangle(&gmesh, verts, vertIndex, 2, st);
	gmesh.obj.materialType = DIFFUSE_AND_GLOSSY;
	gmesh.obj.destroy = exit_triangle;

	pGscene->lights = gLights;
	//add objects to scene
	add_object(gsph1, pGscene);
	add_object(gsph2, pGscene);
	add_object(gmesh, pGscene);

	int count = pGscene->width * pGscene->height;
	pGscene->framebuffer = calloc(count, sizeof(Color));
}

void exit_scene(struct Scene *p)
{
	struct list_head* cur;;
	struct Object* obj;

	list_for_each(cur, &p->objects) {
		obj = list_entry(cur, struct Object, list);
		if (obj->destroy) {
			obj->destroy(obj);
		}
	}
	free(p->framebuffer);
}

void draw_scene(struct Scene *p)
{
	int i, j, index;

	for(i = 0; i < p->width; i++) {
		for ( j = 0; j < p->height; j++) {
			index = get_point_index(i, j, p->width, p->height);
			if (sampled_point(p->framebuffer[index])) {
				DrawPixel(i, j, p->framebuffer[index]);
			}
		}
	}
}

//up, down: fov
float gfov = 90.0f;
void handle_key(int key)
{
	if(key == KEY_UP)
		gfov += 5.0f;
	else if(key == KEY_DOWN)
		gfov -= 5.0f;
	else if(key == KEY_ENTER)
		gfov = 90.0f;

	if(gfov > 150.0f)
		gfov = 150.0f;
	if(gfov < 30.0f)
		gfov = 30.0f;
}

void show_pos(int key)
{
	handle_key(key);
	ClearBackground(toColor(back_col));

	Camera camera = { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f },
		{0.0f, 1.0f, 0.0f }, 90.0f, 0 };
	camera.fovy = gfov;

	BeginMode3D(camera);

	DrawSphere(gsph1.center, gsph1.radius, BLACK);
	DrawSphere(gsph2.center, gsph2.radius, VIOLET);

	Vector3 c1 = {0.815, 0.235, 0.031};
	Vector3 c2 = {0.937, 0.937, 0.231};
	DrawTriangle3D(gmesh.vertices[0], gmesh.vertices[1],
		gmesh.vertices[3], toColor(c1));
	DrawTriangle3D(gmesh.vertices[1], gmesh.vertices[2],
		gmesh.vertices[3], toColor(c2));

	EndMode3D();
}

int main(int argc, char** argv)
{
	// Initialization
	int key;

	init_scene(&gScene);
#ifndef DEBUG_VER
	Render(&gScene);
#endif
	//--------------------------------------------------------------------------------------

	InitWindow(gScene.width, gScene.height, "homework5 - ray tracing");
	SetTargetFPS(60);               // Set our game to run at 60 frames-per-second
	//--------------------------------------------------------------------------------------

	// Main game loop
	while (!WindowShouldClose())    // Detect window close button or ESC key
	{
		// Update
		//----------------------------------------------------------------------------------
		// TODO: Update your variables here
		key = GetKeyPressed();
		if (key == ESC_KEY) {
			break;
		}

		//----------------------------------------------------------------------------------

		// Draw
		//----------------------------------------------------------------------------------
		BeginDrawing();

#ifdef DEBUG_VER
		show_pos(key);
#else
		ClearBackground(RAYWHITE);
		draw_scene(&gScene);
#endif

		EndDrawing();
		//----------------------------------------------------------------------------------
	}

	// De-Initialization
	//--------------------------------------------------------------------------------------
	CloseWindow();        // Close window and OpenGL context
	//--------------------------------------------------------------------------------------

	exit_scene(&gScene);
	return 0;
}
